import urllib.parse
import requests
import json

from coin.blockchain.web3.tron.constants import TRON_TOKEN_TO_ADDRESS


class TronWalletException(Exception):
    "empty or inactivated wallet"
    pass


class TronNativePrivateClient:
  def __init__(self, *, node_info=None, api_key=None, timeout=60):
    self._host = node_info or 'https://api.trongrid.io/'
    self._api_key = api_key
    self._timeout = timeout

  def _get(self, path, params=None):
    url = urllib.parse.urljoin(f'{self._host}/v1/', path)
    params = params or {}
    headers = {}
    if self._api_key:
      headers['TRON-PRO-API-KEY'] = self._api_key
    response = requests.request('GET',
                                url,
                                headers=headers,
                                params=params,
                                timeout=self._timeout)
    response.raise_for_status()
    content = response.json()
    return content['data']

  def get_hex_address(self, base58_address):
    balance_info = self._get(f'accounts/{base58_address}')
    if not balance_info:
      raise TronWalletException
    return balance_info[0]['address']

  def get_latest_balance(self, address, token_address):
    balance_info = self._get(f'accounts/{address}')
    if not balance_info:
      raise TronWalletException
    if TRON_TOKEN_TO_ADDRESS['TRX'] == token_address:
      balance = balance_info[0]['balance']
    else:
      balance_dict = dict((key,d[key]) for d in balance_info[0]['trc20'] for key in d)
      balance = balance_dict.get(token_address, 0)
    return float(balance)

  def get_trx_balance(self, address, end_timestamp=None):
    balance = self.get_latest_balance(address, 
      TRON_TOKEN_TO_ADDRESS['TRX'])
    if end_timestamp is not None:
      latest_timestamp = self.get_latest_block_number_timestamp()[1]
      if end_timestamp != latest_timestamp:
        transactions_to_adjust = self.get_trx_transactions(
            address, end_timestamp, latest_timestamp)
        balance -= transactions_to_adjust
    return balance

  def get_token_balance(
      self,
      address,
      token_address,
      end_height=None
  ):
    assert token_address is not None
    end_timestamp = self.get_block_timestamp(end_height) if end_height else None
    if TRON_TOKEN_TO_ADDRESS['TRX'] == token_address:
      return self.get_trx_balance(address, end_timestamp)
    else:
      balance = self.get_latest_balance(address, token_address)
      if end_timestamp is not None:
        latest_timestamp = self.get_latest_block_number_timestamp()[1]
        if end_timestamp != latest_timestamp:
          transactions_to_adjust = self.get_token_transactions(
              address, token_address, end_timestamp, latest_timestamp)
          balance -= transactions_to_adjust
      return balance

  def get_trx_transactions(self, address, start_timestamp, end_timestamp):
    transact_info = self._get(f'accounts/{address}/transactions', 
      params={'only_confirmed': True, 'limit': 100})
    assert transact_info is not None
    hex_address = self.get_hex_address(address)
    raw_txs = 0
    for trans in transact_info:
      if trans.get('block_timestamp', 0) > start_timestamp and \
        trans.get('block_timestamp', 0) <= end_timestamp:
          ret = trans['ret'][0]
          if ret['contractRet'] != 'SUCCESS':
            continue
          contract = trans['raw_data']['contract'][0]
          if contract['type'] == 'TransferContract':
            to_address = contract['parameter']['value']['to_address']
            from_address = contract['parameter']['value']['owner_address']
            amount = float(contract['parameter']['value']['amount'])
            if to_address == hex_address:
              raw_txs += amount
            elif from_address == hex_address:
              raw_txs = raw_txs - amount - ret['fee']
    return raw_txs

  def get_token_transactions(self, address, token_address, start_timestamp, end_timestamp):
    transact_info = self._get(f'accounts/{address}/transactions/trc20', 
      params={'only_confirmed': True, 'limit': 100})
    assert transact_info is not None
    raw_txs = 0
    for trans in transact_info:
      if trans.get('block_timestamp', 0) > start_timestamp and \
        trans.get('block_timestamp', 0) <= end_timestamp:
          if not trans.get('transaction_id', ''):
            continue
          if trans['token_info']['address'] == token_address:
            to_address = trans['to']
            from_address = trans['from']
            amount = float(trans['value'])
            if to_address == address:
              raw_txs += amount
            elif from_address == address:
              raw_txs -= amount
    return raw_txs

  def get_latest_block_number_timestamp(self):
    block_info = self._get('blocks/latest/events')
    return (block_info[0]['block_number'],
            block_info[0]['block_timestamp'] * 10 ** 6)

  def get_block_timestamp(self, block_number):
    block_info = self._get(f'blocks/{block_number}/events')
    return block_info[0]['block_timestamp'] * 10 ** 6
